/**
 * Copyright 2010-present Facebook.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.internal;

import android.util.Log;
import com.facebook.LoggingBehavior;
import com.facebook.Settings;

import java.util.HashMap;
import java.util.Map;

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for Android. Use of
 * any of the classes in this package is unsupported, and they may be modified or removed without warning at
 * any time.
 */
public class Logger
{
  public static final String LOG_TAG_BASE = "FacebookSDK.";
  private static final HashMap<String, String> stringsToReplace = new HashMap<String, String>();

  private final LoggingBehavior behavior;
  private final String tag;
  private StringBuilder contents;
  private int priority = Log.DEBUG;

  // Note that the mapping of replaced strings is never emptied, so it should be used only for things that
  // are not expected to be too numerous, such as access tokens.
  public synchronized static void registerStringToReplace(String original, String replace)
  {
    stringsToReplace.put(original, replace);
  }

  public synchronized static void registerAccessToken(String accessToken)
  {
    if (Settings.isLoggingBehaviorEnabled(LoggingBehavior.INCLUDE_ACCESS_TOKENS) == false)
    {
      registerStringToReplace(accessToken, "ACCESS_TOKEN_REMOVED");
    }
  }

  public static void log(LoggingBehavior behavior, String tag, String string)
  {
    log(behavior, Log.DEBUG, tag, string);
  }

  public static void log(LoggingBehavior behavior, String tag, String format, Object... args)
  {
    if (Settings.isLoggingBehaviorEnabled(behavior))
    {
      String string = String.format(format, args);
      log(behavior, Log.DEBUG, tag, string);
    }
  }

  public static void log(LoggingBehavior behavior, int priority, String tag, String string)
  {
    if (Settings.isLoggingBehaviorEnabled(behavior))
    {
      string = replaceStrings(string);
      if (tag.startsWith(LOG_TAG_BASE) == false)
      {
        tag = LOG_TAG_BASE + tag;
      }
      Log.println(priority, tag, string);

      // Developer errors warrant special treatment by printing out a stack trace, to make both more noticeable,
      // and let the source of the problem be more easily pinpointed.
      if (behavior == LoggingBehavior.DEVELOPER_ERRORS)
      {
        (new Exception()).printStackTrace();
      }
    }
  }

  private synchronized static String replaceStrings(String string)
  {
    for (Map.Entry<String, String> entry : stringsToReplace.entrySet())
    {
      string = string.replace(entry.getKey(), entry.getValue());
    }
    return string;
  }

  public Logger(LoggingBehavior behavior, String tag)
  {
    Validate.notNullOrEmpty(tag, "tag");

    this.behavior = behavior;
    this.tag = LOG_TAG_BASE + tag;
    this.contents = new StringBuilder();
  }

  public int getPriority()
  {
    return priority;
  }

  public void setPriority(int value)
  {
    Validate.oneOf(value, "value", Log.ASSERT, Log.DEBUG, Log.ERROR, Log.INFO, Log.VERBOSE, Log.WARN);

    priority = value;
  }

  public String getContents()
  {
    return replaceStrings(contents.toString());
  }

  // Writes the accumulated contents, then clears contents to start again.
  public void log()
  {
    logString(contents.toString());
    contents = new StringBuilder();
  }

  // Immediately logs a string, ignoring any accumulated contents, which are left unchanged.
  public void logString(String string)
  {
    log(behavior, priority, tag, string);
  }

  public void append(StringBuilder stringBuilder)
  {
    if (shouldLog())
    {
      contents.append(stringBuilder);
    }
  }

  public void append(String string)
  {
    if (shouldLog())
    {
      contents.append(string);
    }
  }

  public void append(String format, Object... args)
  {
    if (shouldLog())
    {
      contents.append(String.format(format, args));
    }
  }

  public void appendKeyValue(String key, Object value)
  {
    append("  %s:\t%s\n", key, value);
  }

  private boolean shouldLog()
  {
    return Settings.isLoggingBehaviorEnabled(behavior);
  }
}
